#include <cstring>

#include "RedisMng.h"
#include "inicpp.hpp"

#pragma region Redis连接池
RedisConPool::RedisConPool(const size_t pool_size, const char* host, const int port, const char* password = "")
    : _host(host), _port(port), _pool_size(pool_size), _b_stop(false) {
    for (size_t i = 0; i < _pool_size; i++) {
        auto* context = redisConnect(host, port);
        if (context == nullptr || context->err != 0) {
            if (context != nullptr) {
                redisFree(context);
            }
            throw std::runtime_error("redisConnect failed");
        }
        if (password != "") {
            const auto reply = static_cast<redisReply*>(redisCommand(context, "AUTH %s", password));
            if (reply->type == REDIS_REPLY_ERROR) {
                //执行成功 释放redisCommand执行后返回的redisReply所占用的内存
                freeReplyObject(reply);
                throw std::runtime_error("redis password error");
            }
            //执行成功 释放redisCommand执行后返回的redisReply所占用的内存
            freeReplyObject(reply);
        }
        _connections.push(context);
    }
    std::clog << "Redis连接成功，Host: " << host << " ，Port: " << port << " ，Password: " << password << " ，PoolSize: " <<
        pool_size << std::endl;
}

RedisConPool::~RedisConPool() {
    std::lock_guard<std::mutex> lock(_mutex);
    while (!_connections.empty()) {
        _connections.pop();
    }
    std::clog << "RedisConPool::~RedisConPool" << std::endl;
}

redisContext* RedisConPool::GetConnection() {
    std::unique_lock<std::mutex> lock(_mutex);
    _cond.wait(lock, [this] {
        if (_b_stop) {
            return true;
        }
        return !_connections.empty();
    });
    //如果停止则直接返回空指针
    if (_b_stop) {
        return nullptr;
    }
    auto* context = _connections.front();
    _connections.pop();
    return context;
}

void RedisConPool::ReturnConnection(redisContext* context) {
    std::lock_guard<std::mutex> lock(_mutex);
    if (_b_stop) {
        return;
    }
    _connections.push(context);
    _cond.notify_one();
}

void RedisConPool::Close() {
    _b_stop = true;
    _cond.notify_all();
}
#pragma endregion

#pragma region Redis操作
RedisMng::RedisMng() : _reply(nullptr) {
    //读取配置文件
    inicpp::IniManager _ini("../config/config.ini");
    if (!_ini.isSectionExists("Redis")) {
        std::cout << "section of Redis: not exist" << std::endl;
    }
    else if (!_ini["Redis"].isKeyExist("Host") || !_ini["Redis"].isKeyExist("Port")) {
        std::cout << "Server.Port or Server.Host : not exist!" << std::endl;
    }
    else {
        const auto host = _ini["Redis"]["Host"];
        const auto port = _ini["Redis"]["Port"];
        const auto Password = _ini["Redis"]["Password"];
        const auto PoolSize = _ini["Redis"].toInt("PoolSize");
        _con_pool = std::make_unique<RedisConPool>(PoolSize, host.c_str(), std::stoi(port), Password.c_str());
    }
}

RedisMng::~RedisMng() {
    Close();
}

bool RedisMng::GetByKey(const std::string& key, std::string& value) {
    const auto connect = _con_pool->GetConnection();
    if (connect == nullptr) {
        return false;
    }
    this->_reply = static_cast<redisReply*>(redisCommand(connect, "GET %s", key.c_str()));
    if (this->_reply == nullptr) {
        std::cout << "[ GET  " << key << " ] failed" << std::endl;
        freeReplyObject(this->_reply);
        return false;
    }
    if (this->_reply->type != REDIS_REPLY_STRING) {
        std::cout << "[ GET  " << key << " ] failed" << std::endl;
        freeReplyObject(this->_reply);
        return false;
    }
    value = this->_reply->str;
    freeReplyObject(this->_reply);
    std::cout << "Succeed to execute command [ GET " << key << "  ]" << std::endl;
    return true;
}

bool RedisMng::Set(const std::string& key, const std::string& value) {
    const auto connect = _con_pool->GetConnection();
    if (connect == nullptr) {
        return false;
    }
    //执行redis命令行
    this->_reply = static_cast<redisReply*>(redisCommand(connect, "SET %s %s", key.c_str(), value.c_str()));
    //如果返回NULL则说明执行失败
    if (this->_reply == nullptr) {
        std::cout << "Execute command [ SET " << key << "  " << value << " ] failure ! " << std::endl;
        freeReplyObject(this->_reply);
        return false;
    }
    //如果执行失败则释放连接
    if (!(this->_reply->type == REDIS_REPLY_STATUS && (strcmp(this->_reply->str, "OK") == 0 || strcmp(
        this->_reply->str, "ok") == 0))) {
        std::cout << "Execute command [ SET " << key << "  " << value << " ] failure ! " << std::endl;
        freeReplyObject(this->_reply);
        return false;
    }
    //执行成功 释放redisCommand执行后返回的redisReply所占用的内存
    freeReplyObject(this->_reply);
    std::cout << "Execute command [ SET " << key << "  " << value << " ] success ! " << std::endl;
    return true;
}

bool RedisMng::Auth(const std::string& password) {
    const auto connect = _con_pool->GetConnection();
    if (connect == nullptr) {
        return false;
    }
    this->_reply = static_cast<redisReply*>(redisCommand(connect, "AUTH %s", password.c_str()));
    if (this->_reply->type == REDIS_REPLY_ERROR) {
        std::cout << "认证失败" << std::endl;
        //执行成功 释放redisCommand执行后返回的redisReply所占用的内存
        freeReplyObject(this->_reply);
        return false;
    }
    else {
        //执行成功 释放redisCommand执行后返回的redisReply所占用的内存
        freeReplyObject(this->_reply);
        std::cout << "认证成功" << std::endl;
        return true;
    }
}

bool RedisMng::LPush(const std::string& key, const std::string& value) {
    const auto connect = _con_pool->GetConnection();
    if (connect == nullptr) {
        return false;
    }
    this->_reply = static_cast<redisReply*>(redisCommand(connect, "LPUSH %s %s", key.c_str(), value.c_str()));
    if (this->_reply == nullptr) {
        std::cout << "Execute command [ LPUSH " << key << "  " << value << " ] failure ! " << std::endl;
        freeReplyObject(this->_reply);
        return false;
    }
    if (this->_reply->type != REDIS_REPLY_INTEGER || this->_reply->integer <= 0) {
        std::cout << "Execute command [ LPUSH " << key << "  " << value << " ] failure ! " << std::endl;
        freeReplyObject(this->_reply);
        return false;
    }
    std::cout << "Execute command [ LPUSH " << key << "  " << value << " ] success ! " << std::endl;
    freeReplyObject(this->_reply);
    return true;
}

bool RedisMng::LPop(const std::string& key, std::string& value) {
    const auto connect = _con_pool->GetConnection();
    if (connect == nullptr) {
        return false;
    }
    this->_reply = static_cast<redisReply*>(redisCommand(connect, "LPOP %s ", key.c_str()));
    if (_reply == nullptr || _reply->type == REDIS_REPLY_NIL) {
        std::cout << "Execute command [ LPOP " << key << " ] failure ! " << std::endl;
        freeReplyObject(this->_reply);
        return false;
    }
    value = _reply->str;
    std::cout << "Execute command [ LPOP " << key << " ] success ! " << std::endl;
    freeReplyObject(this->_reply);
    return true;
}

bool RedisMng::RPush(const std::string& key, const std::string& value) {
    const auto connect = _con_pool->GetConnection();
    if (connect == nullptr) {
        return false;
    }
    this->_reply = static_cast<redisReply*>(redisCommand(connect, "RPUSH %s %s", key.c_str(), value.c_str()));
    if (this->_reply == nullptr) {
        std::cout << "Execute command [ RPUSH " << key << "  " << value << " ] failure ! " << std::endl;
        freeReplyObject(this->_reply);
        return false;
    }
    if (this->_reply->type != REDIS_REPLY_INTEGER || this->_reply->integer <= 0) {
        std::cout << "Execute command [ RPUSH " << key << "  " << value << " ] failure ! " << std::endl;
        freeReplyObject(this->_reply);
        return false;
    }
    std::cout << "Execute command [ RPUSH " << key << "  " << value << " ] success ! " << std::endl;
    freeReplyObject(this->_reply);
    return true;
}

bool RedisMng::RPop(const std::string& key, std::string& value) {
    const auto connect = _con_pool->GetConnection();
    if (connect == nullptr) {
        return false;
    }
    this->_reply = static_cast<redisReply*>(redisCommand(connect, "RPOP %s ", key.c_str()));
    if (_reply == nullptr || _reply->type == REDIS_REPLY_NIL) {
        std::cout << "Execute command [ RPOP " << key << " ] failure ! " << std::endl;
        freeReplyObject(this->_reply);
        return false;
    }
    value = _reply->str;
    std::cout << "Execute command [ RPOP " << key << " ] success ! " << std::endl;
    freeReplyObject(this->_reply);
    return true;
}

bool RedisMng::HSet(const std::string& key, const std::string& hkey, const std::string& value) {
    const auto connect = _con_pool->GetConnection();
    if (connect == nullptr) {
        return false;
    }
    this->_reply = static_cast<redisReply*>(redisCommand(connect, "HSET %s %s %s", key.c_str(), hkey.c_str(),
                                                         value.c_str()));
    if (_reply == nullptr || _reply->type != REDIS_REPLY_INTEGER) {
        std::cout << "Execute command [ HSet " << key << "  " << hkey << "  " << value << " ] failure ! " << std::endl;
        freeReplyObject(this->_reply);
        return false;
    }
    std::cout << "Execute command [ HSet " << key << "  " << hkey << "  " << value << " ] success ! " << std::endl;
    freeReplyObject(this->_reply);
    return true;
}

bool RedisMng::HSet(const char* key, const char* hkey, const char* hvalue, const size_t hvaluelen) {
    const auto connect = _con_pool->GetConnection();
    if (connect == nullptr) {
        return false;
    }
    const char* argv[4];
    size_t argvlen[4];
    argv[0] = "HSET";
    argvlen[0] = 4;
    argv[1] = key;
    argvlen[1] = strlen(key);
    argv[2] = hkey;
    argvlen[2] = strlen(hkey);
    argv[3] = hvalue;
    argvlen[3] = hvaluelen;
    this->_reply = static_cast<redisReply*>(redisCommandArgv(connect, 4, argv, argvlen));
    if (_reply == nullptr || _reply->type != REDIS_REPLY_INTEGER) {
        std::cout << "Execute command [ HSet " << key << "  " << hkey << "  " << hvalue << " ] failure ! " << std::endl;
        freeReplyObject(this->_reply);
        return false;
    }
    std::cout << "Execute command [ HSet " << key << "  " << hkey << "  " << hvalue << " ] success ! " << std::endl;
    freeReplyObject(this->_reply);
    return true;
}

std::string RedisMng::HGet(const std::string& key, const std::string& hkey) {
    const auto connect = _con_pool->GetConnection();
    if (connect == nullptr) {
        return "";
    }
    const char* argv[3];
    size_t argvlen[3];
    argv[0] = "HGET";
    argvlen[0] = 4;
    argv[1] = key.c_str();
    argvlen[1] = key.length();
    argv[2] = hkey.c_str();
    argvlen[2] = hkey.length();
    this->_reply = static_cast<redisReply*>(redisCommandArgv(connect, 3, argv, argvlen));
    if (this->_reply == nullptr || this->_reply->type == REDIS_REPLY_NIL) {
        freeReplyObject(this->_reply);
        std::cout << "Execute command [ HGet " << key << " " << hkey << "  ] failure ! " << std::endl;
        return "";
    }
    std::string value = this->_reply->str;
    freeReplyObject(this->_reply);
    std::cout << "Execute command [ HGet " << key << " " << hkey << " ] success ! " << std::endl;
    return value;
}

bool RedisMng::Del(const std::string& key) {
    const auto connect = _con_pool->GetConnection();
    if (connect == nullptr) {
        return false;
    }
    this->_reply = static_cast<redisReply*>(redisCommand(connect, "DEL %s", key.c_str()));
    if (this->_reply == nullptr || this->_reply->type != REDIS_REPLY_INTEGER) {
        std::cout << "Execute command [ Del " << key << " ] failure ! " << std::endl;
        freeReplyObject(this->_reply);
        return false;
    }
    std::cout << "Execute command [ Del " << key << " ] success ! " << std::endl;
    freeReplyObject(this->_reply);
    return true;
}

bool RedisMng::ExistsKey(const std::string& key) {
    const auto connect = _con_pool->GetConnection();
    if (connect == nullptr) {
        return false;
    }
    this->_reply = static_cast<redisReply*>(redisCommand(connect, "exists %s", key.c_str()));
    if (this->_reply == nullptr || this->_reply->type != REDIS_REPLY_INTEGER || this->_reply->integer == 0) {
        std::cout << "Not Found [ Key " << key << " ]  ! " << std::endl;
        freeReplyObject(this->_reply);
        return false;
    }
    std::cout << " Found [ Key " << key << " ] exists ! " << std::endl;
    freeReplyObject(this->_reply);
    return true;
}

void RedisMng::Close() const {
    _con_pool->Close();
}
#pragma endregion
